﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace GreyGate.Objects
{
    class Labyrinth
    {
        private Room[,] labyrinth;
        private Room entrance;
        private Room exit;
        private Random rnd;

        public Labyrinth(ContentManager content, int labyrinthSize, bool randomLabyrinth)
        {
            rnd = new Random();
            labyrinth = new Room[labyrinthSize, labyrinthSize];
            if (randomLabyrinth)
            {
                initializeRandomLabyrinth(content, labyrinthSize);
            }
            else
            {
                initializeMaxLabyrinth(content, labyrinthSize);
            }
        }



        public void initializeMaxLabyrinth(ContentManager content, int labyrinthSize)
        {
            // initialisieren der zufälligen Positionen des Eingangs und des Ausgangs.
            Vector2 entrance = new Vector2(rnd.Next(labyrinthSize), 0);
            Vector2 exit = new Vector2(rnd.Next(labyrinthSize), labyrinthSize - 1);

            // erzeugen der Raum-Instanzen anhand der eben festgelegten Positionen und gleichzeitiges anlegen der 
            // Instanzen im Labyrinth-Array.
            this.entrance = labyrinth[(int)entrance.Y, (int)entrance.X] = new Room(content, ref labyrinth, entrance, true, false);
            this.exit = labyrinth[(int)exit.Y, (int)exit.X] = new Room(content, ref labyrinth, exit, false, true);

            for(int y = 0; y < labyrinthSize; y++)
            {
                for (int x = 0; x < labyrinthSize; x++ )
                {
                    if (labyrinth[y, x] == null)
                    {
                        labyrinth[y, x] = new Room(content, ref labyrinth, new Vector2(x,y), false, false);
                        if (labyrinth[y, x].getRoom == null)
                        {
                            labyrinth[y, x] = null;
                        }
                    }
                }                
            }                
        }
        
        public void initializeRandomLabyrinth(ContentManager content, int labyrinthSize)
        {
            #region initializeLabyrinth
                // initialisieren der zufälligen Positionen des Eingangs und des Ausgangs.
                Vector2 entrance = new Vector2(rnd.Next(labyrinthSize), 0);
                Vector2 exit = new Vector2(rnd.Next(labyrinthSize), labyrinthSize - 1);

                // erzeugen der Raum-Instanzen anhand der eben festgelegten Positionen und gleichzeitiges anlegen der 
                // Instanzen im Labyrinth-Array.
                this.entrance = labyrinth[(int)entrance.Y, (int)entrance.X] = new Room(content, ref labyrinth, entrance, true, false);
                this.exit = labyrinth[(int)exit.Y, (int)exit.X] = new Room(content, ref labyrinth, exit, false, true);

                // anlegen der Liste, in der alle Räume ohne vollständigen Anschluss gespeichert werden.
                List<Room> notReady = new List<Room>();
                // Der Eingang wird initial in die Liste eingefügt.
                notReady.Add(this.entrance);

                // Speichern die Räume, die vond en Randseiten, mit Ausnahme von oben am weitesten innen liegen.
                float[] outerX = new float[labyrinthSize];
                float[] innerX = new float[labyrinthSize];
                float[] outerZ = new float[labyrinthSize];

                // Da die innerX die innersten Räume der zeilen speichern sollen, müssen diese Werte zuvor
                // auf einen höheren Wert als die Größe des Labyrinths gesetzt werden.
                for (int x = 0; x < innerX.Count(); x++)
                {
                    innerX[x] = labyrinthSize;
                }
                // Hier werden die Koordinaten von rechts begutachtet, und daher muss der Startwert kleiner
                // als 0 sein.
                for (int x = 0; x < outerX.Count(); x++)
                {
                    outerX[x] = -1;
                }
                // Hier werden die Koordinaten von unten gesehen betrachtet, weshalb der Startwert ebenfalls
                // kleiner als 0 sein muss.
                for (int x = 0; x < outerZ.Count(); x++)
                {
                    outerZ[x] = -1;
                }

                // so lange der Ausgang noch keinen vollständigen Anschluss hat
                while (!this.exit.isRoomFullySet())
                {
                    // so lange es noch Räume gibt, die keinen vollständigen Anschluss haben.
                    // Der Ausgang befindet sich nicht in dieser Liste.
                    while (notReady.Count > 0)
                    {
                        List<Vector2> freeWays = notReady.ElementAt(0).getFreeWays();

                        while (freeWays.Count > 0)
                        {
                            Room newRoom =
                                          labyrinth[(int)freeWays.ElementAt(0).Y, (int)freeWays.ElementAt(0).X] =
                                                            new Room(ref labyrinth, content, freeWays.ElementAt(0));
                            if (outerX[(int)freeWays.ElementAt(0).Y] < freeWays.ElementAt(0).X)
                            {
                                outerX[(int)freeWays.ElementAt(0).Y] = freeWays.ElementAt(0).X;
                            }

                            if (innerX[(int)freeWays.ElementAt(0).Y] > freeWays.ElementAt(0).X)
                            {
                                innerX[(int)freeWays.ElementAt(0).Y] = freeWays.ElementAt(0).X;
                            }

                            if (outerZ[(int)freeWays.ElementAt(0).X] < freeWays.ElementAt(0).Y)
                            {
                                outerZ[(int)freeWays.ElementAt(0).X] = freeWays.ElementAt(0).Y;
                            }

                            if (!newRoom.isRoomFullySet())
                            {
                                notReady.Add(newRoom);
                            }
                            freeWays.RemoveAt(0);
                        }
                        notReady.RemoveAt(0);
                    }

                    if (!this.exit.isRoomFullySet())
                    {
                        List<Vector2> indexes = new List<Vector2>();
                        Vector2 value = new Vector2(),
                                value1 = new Vector2(),
                                value2 = new Vector2();
                        int zufall = rnd.Next(labyrinthSize);

                        do
                        {
                            zufall = rnd.Next(labyrinthSize);
                            value = new Vector2(innerX[zufall], zufall);
                        } while (innerX[zufall] == labyrinthSize);
                        indexes.Add(value);

                        do
                        {
                            zufall = rnd.Next(labyrinthSize);
                            value1 = new Vector2(outerX[zufall], zufall);
                        } while (outerX[zufall] == -1);

                        indexes.Add(value1);

                        do
                        {
                            zufall = rnd.Next(labyrinthSize);
                            value1 = new Vector2(outerZ[zufall], zufall);
                        } while (outerZ[zufall] == -1);

                        indexes.Add(value2);


                        for (int x = 0; x < indexes.Count; x++)
                        {
                            Room newRoom = labyrinth[(int)indexes.ElementAt(x).Y, (int)indexes.ElementAt(x).X];
                            if (newRoom != null)
                            {
                                newRoom.overrideToMaxRoom(ref labyrinth, content);
                                newRoom = labyrinth[(int)indexes.ElementAt(x).Y, (int)indexes.ElementAt(x).X];
                                notReady.Add(newRoom);
                            }

                        }
                    }
                }
                #endregion
        }
            
        

        #region Getter und Setter
        public Room[,] getLabyrinth
        {
            get { return labyrinth; }
        }
        public Room getRoomAt(int y, int x)
        {
            return labyrinth[y,x];
        }
        public Room Entrance
        {
            get { return entrance; }
        }
        public Room Exit
        {
            get { return exit; }
        }
        #endregion

    }
}
